package lib;

import static util.Constant.FILE_USERS;
import static util.Constant.PATH_DATA;
import static util.Constant.STR_LOGIN_ALREADY_EXISTS;
import static util.Constant.STR_USER_NOT_CONNECTED;
import static util.Tool.Sha1;
import static util.Tool.getMaxID;
import io.InOutput;

import java.util.Enumeration;
import java.util.HashSet;
import java.util.Hashtable;
import java.util.Set;

import base.User;

/**
 * The UserManager {@code class}. Manage the users
 * 
 * @author Peter NGO, Guillaume POUSSEL and Mathieu RUL
 * @version 1.0
 */
public class UserManager {

	/**
	 * Get an instance of {@code UserManager}.
	 * 
	 * @return an instance of {@code UserManager}.
	 */
	public static UserManager getInstance() {
		if (managerInstance == null) {
			try {
				managerInstance = new UserManager();
			} catch (Exception e) {
				e.printStackTrace();
			}
		}
		return managerInstance;
	}

	/**
	 * The {@code String} containing the path of User data.
	 */
	private String path = PATH_DATA + FILE_USERS;

	/**
	 * The current User.
	 */
	private User currentUser = null;
	/**
	 * The {@code UserManager} instance.
	 */
	private static UserManager managerInstance;

	/**
	 * The {@code Hashtable} containing the User object.
	 */
	private Hashtable<Integer, Object> userTable;

	/**
	 * Constructor of the {@code class}. Create the user manager.
	 * 
	 * @throws Exception
	 */
	private UserManager() throws Exception {
		initialize();
		load();
	}

	/**
	 * Add an ItemList to current user.
	 * 
	 * @param ID
	 *            the ItemList's ID.
	 * 
	 * @throws Exception
	 */
	public void addItemlist(Integer ID) throws Exception {
		currentUser.addItemList(ID);
		save();
	}

	/**
	 * Test if two user are friends.
	 * 
	 * @param user1
	 *            the first user to test.
	 * 
	 * @param user2
	 *            the second user to test.
	 * 
	 * @return {@code true} if user are friends; {@code false} otherwise.
	 */
	public Boolean areFriends(User user1, User user2) throws Exception {
		return user1.isFriend(user2.getID()) && user2.isFriend(user1.getID());
	}

	/**
	 * Connect an user to his account.
	 * 
	 * @param login
	 *            the user login.
	 * 
	 * @param password
	 *            the user password.
	 * 
	 * @return {@code true} if user is connected; {@code false} otherwise.
	 */
	public Boolean connect(String login, String password) {
		// Looking for User with correct login
		Enumeration<Integer> e = userTable.keys();
		while (e.hasMoreElements()) {
			User user = (User) userTable.get(e.nextElement());
			if (user.getLogin().equals(login)) {
				if (user.connect(password)) {
					setCurrentUser(user);
					return true;
				}
			}
		}
		return false;
	}

	/**
	 * Create a new User.
	 * 
	 * @param login
	 *            the user login.
	 * 
	 * @param password
	 *            the user password.
	 * 
	 * @param lastname
	 *            the user lastname.
	 * 
	 * @param firstname
	 *            the user firstname.
	 * 
	 * @return Integer the ID of created user
	 * 
	 * @throws IllegalArgumentException
	 */
	public User create(String login, String password, String lastname,
			String firstname) {
		// Looking if looking not exist
		Enumeration<Integer> e = userTable.keys();
		while (e.hasMoreElements()) {
			User user = (User) userTable.get(e.nextElement());
			if (user.getLogin().equals(login))
				throw new IllegalArgumentException(STR_LOGIN_ALREADY_EXISTS);
		}
		Integer ID = getMaxID(userTable) + 1;
		User user = new User(ID, login, Sha1(password), firstname, lastname,
				new HashSet<Integer>(), new HashSet<Integer>());
		userTable.put(ID, user);
		save();
		return user;
	}

	/**
	 * Disconnect an User.
	 */
	public void disconnect() {
		currentUser.disconnect();
		this.currentUser = null;
	}

	/**
	 * Get the User from the ID.
	 * 
	 * @return the User.
	 * 
	 * @throws Exception
	 */
	public User get(Integer ID) throws Exception {
		return (User) userTable.get(ID);
	}

	/**
	 * Get the User from the login.
	 * 
	 * @return the User.
	 * 
	 */
	public User get(String login) {
		Enumeration<Integer> e = userTable.keys();
		while (e.hasMoreElements()) {
			User user = (User) userTable.get(e.nextElement());
			if (user.getLogin().equals(login))
				return user;
		}
		return null;
	}

	public Set<User> getAll() {
		Set<User> users = new HashSet<User>();
		for (Object obj : userTable.values()) {
			users.add((User) obj);
		}
		return users;
	}

	/**
	 * Get the current user.
	 * 
	 * @return the currentUser.
	 */
	public User getCurrentUser() {
		return this.currentUser;
	}

	public Set<User> getFriends(Integer userid) {
		try {
			return this.getFriends(get(userid));
		} catch (Exception e) {
			return null;
		}
	}

	public Set<User> getFriends(User user) {

		Set<User> users = new HashSet<User>();
		for (Integer friendid : user.getFriends()) {
			try {
				users.add(this.get(friendid));
			} catch (Exception e) {
			}
		}
		return users;
	}

	/**
	 * Initialize the instance.
	 */
	public void initialize() {
		userTable = new Hashtable<Integer, Object>();
	}

	public boolean isFriendItemlist(Integer itemlistId) {
		for (User friend : getFriends(currentUser)) {
			return friend.getItemLists().contains(itemlistId);
		}
		return false;
	}

	/**
	 * Test if two users are friends.
	 * 
	 * @param user1
	 *            the first user to test.
	 * 
	 * @param user2
	 *            the second user to test.
	 * 
	 * @throws Exception
	 */
	public Boolean isFriends(User user1, User user2) throws Exception {
		return user1.isFriend(user2.getID()) && user2.isFriend(user1.getID());
	}

	public boolean isOwnedItemlist(Integer itemlistId) {
		return currentUser.getItemLists().contains(itemlistId);
	}

	/**
	 * Load the database in user table.
	 * 
	 * @throws Exception
	 */
	private void load() throws Exception {
		userTable = InOutput.read(path);
	}

	/**
	 * Make friend two user.
	 * 
	 * @param user1
	 *            the first user to make friend should be connected.
	 * 
	 * @param user2
	 *            the second user to make friend.
	 * 
	 * @throws Exception
	 */
	public void makeFriends(User user1, User user2) throws Exception {
		if (!user1.isConnected())
			throw new IllegalArgumentException(STR_USER_NOT_CONNECTED);
		user1.addFriend(user2.getID());
		user2.addFriend(user1.getID());
		save();
	}

	/**
	 * Remove an user.
	 * 
	 * @param ID
	 *            the user ID to remove.
	 * 
	 * @throws Exception
	 */
	public void remove(Integer ID) throws Exception {
		for (Integer itemListID : this.get(ID).getItemLists()) {
			ItemlistManager.getInstance().remove(itemListID);
		}
		userTable.remove(ID);
		save();
	}

	/**
	 * Un make friends two user.
	 * 
	 * @param user1
	 *            the first user to unmake friend should be connected.
	 * 
	 * @param user2
	 *            the second user to unmake friend.
	 * 
	 * @throws Exception
	 */
	public void removeFriends(User user1, User user2) throws Exception {
		if (!user1.isConnected())
			throw new IllegalArgumentException(STR_USER_NOT_CONNECTED);
		user1.removeFriend(user2.getID());
		user2.removeFriend(user1.getID());
		save();
	}

	/**
	 * Save the user table in database.
	 */
	public void save() {
		InOutput.write(userTable, path);
	}

	/**
	 * Set the current User.
	 * 
	 * @param currentUser
	 *            the currentUser to set.
	 */
	public void setCurrentUser(User currentUser) {
		this.currentUser = currentUser;
	}

	/**
	 * Set the path.
	 * 
	 * @param path
	 *            the path to set.
	 */
	public void setPath(String path) {
		this.path = path;
	}

	/**
	 * Returns a {@code String} object representing the all the User's value
	 * contains in the UserManager.
	 * 
	 * @return a string representation of all the User.
	 */
	@Override
	public String toString() {
		String result = "";
		Enumeration<Integer> e = userTable.keys();
		while (e.hasMoreElements()) {
			User user = (User) userTable.get(e.nextElement());
			result += user.profile() + "\n";
		}
		return result;
	}
}